#include <cstdlib>
#include <cstdio>
#include <string>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include "RSAKey.h"

namespace crypto
{
  namespace rsa
  {

    RSAKey::RSAKey()
    {
      this->key = NULL;
      this->type = UNDEFINED;
    }

    RSAKey::RSAKey(std::string filename, KeyType type)
    {
      this->readKeyFromFile(filename, type);
    }

    RSAKey::~RSAKey()
    {
      if (this->key != NULL)
        RSA_free(key);
    }

    RSA * RSAKey::toRSA()
    {
      if (this->key == NULL)
      {
        throw "key is not initialized!";
      }
      return this->key;
    }

    void RSAKey::printKey()
    {
      if (this->key == NULL)
      {
        throw "key is not initialized!";
      }
      RSA_print_fp(stdout, this->key, 0);
    }      

    int RSAKey::size()
    {
      if (this->key == NULL)
      {
        throw "key is not initialized!";
      }
      return RSA_size(this->key);
    }

    RSAKey::KeyType RSAKey::getType()
    {
      return this->type;
    }

    void RSAKey::readKeyFromFile(std::string filename, KeyType type)
    {
      FILE *keyfile = fopen(filename.c_str(), "rt");
      if (keyfile == NULL)
      {
        throw "cannot open file!";
      }

      switch (type)
      {
        case UNDEFINED:
          throw "impossible key type!";
          break;
        case PUBLIC:
          this->key = PEM_read_RSAPublicKey(keyfile, NULL, NULL, NULL);
          break;
        case PRIVATE:
          this->key = PEM_read_RSAPrivateKey(keyfile, NULL, NULL, NULL);
          break;
        default:
          throw "unknown key type!";
      }
      this->type = type;

      if (this->key == NULL)
      {
        throw "key was not read!";
      }
      if (!RSA_check_key(this->key))
      {
        throw "key was read incorrectly!";
      }

      fclose(keyfile);
    }

    void RSAKey::generatePrivateKey(int size)
    {
      if (size < 1024)
      {
        throw "size is wrong or insecure!";
      }

      if (this->key != NULL)
      {
        RSA_free(this->key);
      }

      this->key = RSA_generate_key(size, this->EXPONENT, NULL, NULL);
      if (this->key == NULL)
      {
        throw "key was not created correctly!";
      }
      if (!RSA_check_key(this->key))
      {
        throw "key was not created correctly!";
      }

      this->type = RSAKey::PRIVATE;
    }

    void RSAKey::generatePublicKey(RSAKey *privateKey)
    {
      /*
      if (privateKey == this)
      {
        throw "asdfas";
      }
      */
      if (privateKey->getType() != RSAKey::PRIVATE)
      {
        throw "wrong input key type!";
      }

      RSA *ptr = RSA_new();
      if (ptr == NULL)
      {
        throw "cannot allocate memory!";
      }

      ptr = RSAPublicKey_dup(privateKey->toRSA());

      /*
      if (this->key == NULL)
      {
        RSA_free(this->key);
      }

      */
      this->type = PUBLIC;
      this->key = ptr;

    }

  } //namespace rsa
} //namespace crypto
